#pragma once

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <websocketpp/frame.hpp>
typedef websocketpp::server<websocketpp::config::asio> server;
typedef websocketpp::connection_hdl con_hdl;
typedef server::message_ptr msg_ptr;

#include <iostream>
#include <vector>
#include <map>
#include <thread>
#include <mysql/mysql.h>
#include <json/json.h>
#include <fstream>
#include <sys/stat.h> // 用于获取文件大小


#include "send_to_qml.hpp"

class hdlWithFileInfo
{
public:
    hdlWithFileInfo()
    {
        // hdl_;
        kind_ = "";
        user_id_ = -1;
        file_size_ = 0;
        file_size_already_ = 0;
        file_name_ = "";
        file_suffix_ = "";
    }
    con_hdl hdl_;
    std::string kind_; // 文件的类型
    int user_id_; // 用户名 id
    int file_size_; // 文件总大小
    int file_size_already_; // 已经上传好的大小
    std::string file_name_; // 文件名字（包括后缀）
    std::string file_suffix_; // 文件后缀
    std::map<int,std::string> data_; // 数据组装器
};


class FileMgt
{

public:
    FileMgt(){};
    ~FileMgt(){};
    void push_back(hdlWithFileInfo in)
    {
        mutex_.lock();
        hdls_with_file_info_.push_back(in);
        mutex_.unlock();
    }
    
    // 根据 hdl 查找 hdlWithFileInfo
    hdlWithFileInfo* file_info_at_hdl(con_hdl h)
    {
        mutex_.lock();
        for(auto& value : hdls_with_file_info_)
        {
            if(value.hdl_.lock().get() == h.lock().get())
            {
                mutex_.unlock();
                return &value;
            }
        }
        mutex_.unlock();
        return nullptr;
    }

    // 返回值代表文件是否已经发送完成, 将二进制数据放入组装器, 累加大小
    bool add_data_to_hdl(int data_num, std::string& data, con_hdl hdl) 
    {
        hdlWithFileInfo* h = file_info_at_hdl(hdl);
        mutex_.lock();
        if(h->file_size_already_ >= h->file_size_) // 第一次判断
        {
            h->file_size_already_ = 0;
            mutex_.unlock();
            return true;  // 文件接收完成
        }
        h->file_size_already_ += data.size();
        h->data_[data_num] = data;
        std::cout << "已经发送的大小: " << h->file_size_already_ << std::endl << std::endl;
        if(h->file_size_already_ >= h->file_size_) // 第二次判断
        {
            h->file_size_already_ = 0;
            mutex_.unlock();
            return true;  // 文件接收完成
        }
        mutex_.unlock();
        return false;
    }

    bool have_thread_id(std::thread::id id)
    {
        mutex_of_thread_ids.lock();
        for(auto value : thread_ids)
        {
            if(value == id)
            {
                mutex_of_thread_ids.unlock(); // 加锁后记得解锁，防止死锁
                return true;
            }
        }
        thread_ids.push_back(id);
        mutex_of_thread_ids.unlock();
        return false;
    }
    void show_thread_ids()
    {
        std::cout << "当前运行过的所有线程" << std::endl;
        mutex_of_thread_ids.lock();
        for(auto value : thread_ids)
        {
            std::cout << value << std::endl;
        }
        mutex_of_thread_ids.unlock();
    }

    bool fileExists(const std::string& filename) 
    {
        std::ifstream file(filename);
        return file.good();
    }
    


    // 到来的二进制数据
    void processing_binary(server *s, con_hdl hdl, msg_ptr msg)
    {
        
        hdlWithFileInfo hdl_with_info = *file_info_at_hdl(hdl); // 拿到一个文件发送的任务

        //std::cout << "文件管理模块 processing_binary " << hdl_with_info.kind_;

        if(hdl_with_info.kind_ == "upload_user_avatar")
        {
            upload_user_avatar(s,hdl,msg,hdl_with_info);
        }
        else if(hdl_with_info.kind_ == "msg_file") 
        {
            upload_file(s,hdl,msg,hdl_with_info);
        }
    }

    /// 上传
    void upload_user_avatar(server *s, con_hdl hdl, msg_ptr msg, hdlWithFileInfo info); // 上传用户头像
    void upload_file(server *s, con_hdl hdl, msg_ptr msg, hdlWithFileInfo info); // 上传文件

    /// 下载
    void send_user_avatar(int user_id, con_hdl hdl, server *s); // 把此id的用户的头像二进制数据发到连接 hdl 上去
    void send_group_avatar(int group_id, con_hdl hdl, server *s); // 把此id的群组的头像二进制数据发到连接 hdl 上去
    void download_file(std::string file_name, server *s, con_hdl hdl);

    /// 辅助接口
    void upload_file_before(std::string file_name, int file_size, con_hdl hdl); // 处理上传文件的请求

private:
    static std::mutex mutex_;
    static std::mutex mutex_of_thread_ids;
    static std::vector<hdlWithFileInfo> hdls_with_file_info_;
    static std::vector<std::thread::id> thread_ids;
};

std::mutex FileMgt::mutex_;
std::mutex FileMgt::mutex_of_thread_ids;
std::vector<hdlWithFileInfo> FileMgt::hdls_with_file_info_;
std::vector<std::thread::id> FileMgt::thread_ids;



// 把此id的用户的头像二进制数据发到连接 hdl 上去
void FileMgt::send_user_avatar(int user_id, con_hdl hdl,server *s)
{

    // 获取头像文件的路径
    // 连接数据库
    MYSQL* mysql = mysql_init(nullptr);
    if(mysql_real_connect(mysql,"127.0.0.1","qbt","134256","WSChat",
    8099,nullptr,0) == nullptr)
    {
        // 连接数据库失败!
        mysql_close(mysql);
        return;
    }
    else
    {
        mysql_set_character_set(mysql,"utf8"); 
    }
    std::string query = 
    "SELECT avatar FROM user WHERE id = '" + std::to_string(user_id) + "'";
    
    mysql_query(mysql, query.c_str());
    MYSQL_RES* result = mysql_store_result(mysql);
    // 查询出错时 row 为 nullptr, 查询到字段但该字段为NULL值时, 是 row[i] 为 nullptr (i 为字段的序号)
    MYSQL_ROW row = mysql_fetch_row(result); 
    // 这里要检查 row[0] 而不是 row, 这两个不一样
    if(row[0] == nullptr)
    {
        // 用户没有设置头像
        std::cout << "4此id的用户没有设置头像: " << user_id << std::endl;
        mysql_close(mysql);
	    unsigned long long file_size = 0;
        s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY);
        return;
    }

    

    // 此用户有头像
    std::string file_path = row[0];
    mysql_close(mysql);

    // 告知客户端文件大小, size_t 为 unsigned long, 占4字节
    struct stat statbuf; // 这是一个存储文件(夹)信息的结构体，其中有文件大小和创建时间、访问时间、修改时间等
	stat(file_path.c_str(), &statbuf); // 提供文件名字符串，获得文件属性结构体
	unsigned long long file_size = statbuf.st_size; // 获取文件大小
    std::cout << "要发送给客户端的文件大小: " << file_size << std::endl;
    s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY); 

    // 打开文件
    std::ifstream ifs;
    ifs.open(file_path,std::ios::in | std::ios::binary);
    if(!ifs.is_open())
    {
        std::cout << "文件传输模块,函数send_user_avatar,打开文件失败" << std::endl;
    }


    //读取并发送文件
    const int buffer_size = 500000; // 50w 字节
    char buffer[buffer_size];
    while (!ifs.eof())
    {
        ifs.read(buffer, buffer_size); // 每次读 buffer_size 个字节出来
        std::streamsize bytesRead = ifs.gcount(); // 实际读取的字节数
        std::cout << "要给客户端发送的头像,读取到的字节数: " << bytesRead <<  std::endl;
        if (bytesRead > 0)
        {
            // std::cout << "准备发送头像文件数据" << std::endl;
            s->send(hdl, buffer, bytesRead, websocketpp::frame::opcode::BINARY);
            // std::cout << "成功发送一次头像文件数据" << std::endl;
        }
    }
    ifs.close();


}

void FileMgt::upload_user_avatar(server *s, con_hdl hdl, msg_ptr msg, hdlWithFileInfo info)
{
    if(info.file_size_already_ < info.file_size_) // 文件还没发完
        {
            std::string data_with_num = msg->get_payload();
            // 从 data_with_num 的前四个字节中读取 int 值
            uint32_t data_num; // 二进制数据包的序号
            std::memcpy(&data_num, data_with_num.data(), sizeof(uint32_t));
            // 转换为主机字节序（如果需要的话）
            data_num = ntohl(data_num); // 二进制数据是网络字节序的话,就需要转换

            //std::cout << "########thread########  " << std::this_thread::get_id() << std::endl;
            //std::cout << "二进制数据包序号：" << intValue << std::endl;
            //if(!have_thread_id(std::this_thread::get_id()))
                //std::cout << "新线程 " << std::this_thread::get_id() << std::endl;
            //have_thread_id(std::this_thread::get_id());
            //show_thread_ids();
            //std::this_thread::sleep_for(std::chrono::seconds(120));
            
            std::string data = data_with_num.substr(4); // 获取从第五个字节开始到结尾的数据

            //std::cout << "二进制数据包大小：" << data.size() << std::endl;

            // 判断是否发送完成,若未完成,增加连接 hdl 上的 “已经发送的数据”
            if(add_data_to_hdl(data_num, data, hdl))
            {
                std::cout << "头像上传完成,准备把文件路径写入数据库" << std::endl;

                // 连接数据库
                MYSQL* mysql = mysql_init(nullptr);
                if(mysql_real_connect(mysql,"127.0.0.1","qbt","134256","WSChat",
                8099,nullptr,0) == nullptr)
                {
                    // 连接数据库失败!
                    mysql_close(mysql);
                    return;
                }
                else
                {
                    mysql_set_character_set(mysql,"utf8"); 
                }


                hdlWithFileInfo info_current = *file_info_at_hdl(hdl);
                int user_id = info_current.user_id_;
                std::map<int,std::string>::iterator it = info_current.data_.begin();
                std::map<int,std::string>::iterator itEnd = info_current.data_.end();


                // 写入文件
                std::string file_path = "./user_avatar/"+std::to_string(user_id)+".png"; // 生成文件路径
                std::ofstream ofs(file_path, std::ios::out|std::ios::binary);

                while(it != itEnd) // 组装二进制数据
                {
                    ofs.write((const char*)it->second.c_str(),it->second.size());
                    it++;
                }

                ofs.close();

                // 把头像文件的路径保存到数据库
                std::string insert_query = 
                "UPDATE user SET avatar = '" + file_path + "' WHERE id = '" + std::to_string(user_id) + "'";
                mysql_query(mysql, insert_query.c_str());

                mysql_close(mysql);


                // 告诉 qml 客户端, 文件传输完了
                Json::Value root;
                root["op"] = "complete";
                std::string jsonString = root.toStyledString();
                send_to_qml::send_text(hdl, jsonString);
            }
        }
        else if (info.file_size_already_ == info.file_size_) // 文件刚好发完
        {
            std::cout << "发生错误,已发送大小超过文件大小" << std::endl;
        }
        else // 发生错误,已发送大小超过文件大小
        {
            std::cout << "发生错误,已发送大小超过文件大小" << std::endl;
        }
}


void FileMgt::upload_file_before(std::string file_name, int file_size, con_hdl hdl)
{
    hdlWithFileInfo hld_with_file;
    hld_with_file.hdl_ = hdl;
    hld_with_file.kind_ = "msg_file";
    hld_with_file.file_name_ = file_name;
    hld_with_file.file_size_ = file_size;
    push_back(hld_with_file); // 信息放入，给接下来要到来的二进制数据定向

    // 告诉客户端,我定向好了,你可以开始发文件了
    Json::Value root;
    root["op"] = "ready";
    std::string jsonString = root.toStyledString();
    send_to_qml::send_text(hdl, jsonString);
}

void FileMgt::upload_file(server *s, con_hdl hdl, msg_ptr msg, hdlWithFileInfo info) // 上传文件
{
    if(info.file_size_already_ < info.file_size_) // 文件还没发完
    {
        std::string data_with_num = msg->get_payload();
        // 从 data_with_num 的前四个字节中读取 int 值
        uint32_t data_num; // 二进制数据包的序号
        std::memcpy(&data_num, data_with_num.data(), sizeof(uint32_t));
        data_num = ntohl(data_num); // 二进制数据是网络字节序,需要转换
        
        // 获取从第五个字节开始到结尾的数据
        std::string data = data_with_num.substr(4); 
        //std::cout << "二进制数据包大小：" << data.size() << std::endl;
        // 判断是否发送完成,若未完成,增加连接 hdl 上的 “已经发送的数据”
        if(add_data_to_hdl(data_num, data, hdl))
        {
            std::cout << "文件上传完成" << std::endl;
            
            hdlWithFileInfo info_current = *file_info_at_hdl(hdl);
            
            // 写入文件
            std::string file_path = "./file/"+ info_current.file_name_; // 生成文件路径
            std::ofstream ofs(file_path, std::ios::out|std::ios::binary);
            // 组装二进制数据
            std::map<int,std::string>::iterator it = info_current.data_.begin();
            std::map<int,std::string>::iterator itEnd = info_current.data_.end();
            while(it != itEnd) 
            {
                ofs.write((const char*)it->second.c_str(),it->second.size());
                it++;
            }
            ofs.close();
            // 告诉 qml 客户端, 文件传输完了
            Json::Value root;
            root["op"] = "complete";
            std::string jsonString = root.toStyledString();
            send_to_qml::send_text(hdl, jsonString);
        }
    }
    else if (info.file_size_already_ == info.file_size_) // 文件刚好发完
    {
        std::cout << "文件刚好发完" << std::endl;
    }
    else // 发生错误,已发送大小超过文件大小
    {
        std::cout << "发生错误,已发送大小超过文件大小" << std::endl;
    }

}


void FileMgt::download_file(std::string file_name, server *s, con_hdl hdl)
{
    // 组合出文件的路径
    std::string path = "./file/" + file_name;
    if(fileExists(path)) {
        std::cout << "要下载的文件存在\n";
    } else {
        std::cout << "要下载的文件不存在\n";
        unsigned long long file_size = 0;
        s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY);
        return;
    }

    

    // 告知客户端文件大小, size_t 为 unsigned long, 占4字节
    struct stat statbuf; // 这是一个存储文件(夹)信息的结构体，其中有文件大小和创建时间、访问时间、修改时间等
	stat(path.c_str(), &statbuf); // 提供文件名字符串，获得文件属性结构体
	unsigned long long file_size = statbuf.st_size; // 获取文件大小
    std::cout << "要发送给客户端的文件大小: " << file_size << std::endl;
    s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY); 


    // 打开文件
    std::ifstream ifs;
    ifs.open(path,std::ios::in | std::ios::binary);
    if(!ifs.is_open())
    {
        std::cout << "文件传输模块,函数send_user_avatar,打开文件失败" << std::endl;
    }


    //读取并发送文件
    const int buffer_size = 500000; // 50w 字节
    char buffer[buffer_size];
    while (!ifs.eof())
    {
        ifs.read(buffer, buffer_size); // 每次读 buffer_size 个字节出来
        std::streamsize bytesRead = ifs.gcount(); // 实际读取的字节数
        std::cout << "要给客户端发送的头像,读取到的字节数: " << bytesRead <<  std::endl;
        if (bytesRead > 0)
        {
            // std::cout << "准备发送头像文件数据" << std::endl;
            s->send(hdl, buffer, bytesRead, websocketpp::frame::opcode::BINARY);
            // std::cout << "成功发送一次头像文件数据" << std::endl;
        }
    }
    ifs.close();
}

void FileMgt::send_group_avatar(int group_id, con_hdl hdl, server *s)
{
    // std::cout << "FileMgt::send_group_avatar 进入函数" << std::endl;
    // 获取头像文件的路径
    // 连接数据库
    MYSQL* mysql = mysql_init(nullptr);
    if(mysql_real_connect(mysql,"127.0.0.1","qbt","134256","WSChat",
    8099,nullptr,0) == nullptr)
    {
        // 连接数据库失败!
        mysql_close(mysql);
        return;
    }
    else
    {
        mysql_set_character_set(mysql,"utf8"); 
    }
    std::string query = 
    "SELECT avatar FROM `group` WHERE id = '" + std::to_string(group_id) + "'";
    
    mysql_query(mysql, query.c_str());

    // std::cout << "FileMgt::send_group_avatar 执行查询语句后" << std::endl;
    mysql_close(mysql);
    
    MYSQL_RES* result = mysql_store_result(mysql);
    if(result == nullptr)
    {
        std::cout << "result == nullptr" << std::endl;
	    unsigned long long file_size = 0;
        s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY);
        return;
    }
    // 查询出错时 row 为 nullptr, 查询到字段但该字段为NULL值时, 是 row[i] 为 nullptr (i 为字段的序号)
    MYSQL_ROW row = mysql_fetch_row(result); 
    // 这里要检查 row[0] 而不是 row, 这两个不一样
    
    if(row[0] == nullptr || row[0] == "" || row[0] == '\0')
    {
        // 该群组没有设置头像
        std::cout << "此id " << group_id << "的群组没有设置头像-";
        if(row[0] == nullptr)
            std::cout << "row[0] == nullptr" << std::endl;
        if(row[0] == "")
            std::cout << "row[0] == \"\"" << std::endl;
        if(row[0] == '\0')
            std::cout << "row[0] == \0" << std::endl;
	    unsigned long long file_size = 0;
        s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY);
        return;
    }
    
    // std::cout << "FileMgt::send_group_avatar 检查row[0]后" << std::endl;
    std::cout << row[0] << std::endl;

    // 打开文件
    std::string file_path = row[0];
    std::ifstream ifs;
    ifs.open(file_path,std::ios::in | std::ios::binary);
    if(!ifs.is_open())
    {
        std::cout << "文件传输模块,函数send_group_avatar,打开文件失败" << std::endl;
	    unsigned long long file_size = 0;
        s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY);
        return;
    }

    // 此群组有头像
    // 告知客户端文件大小, size_t 为 unsigned long, 占4字节
    struct stat statbuf; // 这是一个存储文件(夹)信息的结构体，其中有文件大小和创建时间、访问时间、修改时间等
	stat(file_path.c_str(), &statbuf); // 提供文件名字符串，获得文件属性结构体
	unsigned long long file_size = statbuf.st_size; // 获取文件大小
    std::cout << "要发送给客户端的文件大小: " << file_size << std::endl;
    s->send(hdl, (char*)&file_size, sizeof(file_size), websocketpp::frame::opcode::BINARY); 

    //读取并发送文件
    const int buffer_size = 500000; // 50w 字节
    char buffer[buffer_size];
    while (!ifs.eof())
    {
        ifs.read(buffer, buffer_size); // 每次读 buffer_size 个字节出来
        std::streamsize bytesRead = ifs.gcount(); // 实际读取的字节数
        // std::cout << "要给客户端发送的头像,读取到的字节数: " << bytesRead <<  std::endl;
        if (bytesRead > 0)
        {
            // std::cout << "准备发送头像文件数据" << std::endl;
            s->send(hdl, buffer, bytesRead, websocketpp::frame::opcode::BINARY);
            // std::cout << "成功发送一次头像文件数据" << std::endl;
        }
    }
    ifs.close();
}